############################################################################################
## ui.py
##
## This file contains the core for game user interface display
##
## @author(s): Shadow
## @documentation: Shadow
##
## Created on November 4, 2009
############################################################################################
import pygame,loaders,gameroot,player,math,monitors,time


######################################################################################
## UIDrawable
##
## This class represents a drawable object on the user interface
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class UIDrawable:
    
    ###########################################################
    ## UIDrawable::__init__
    ##
    ## Constructor for the UIDrawable class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root,imgloc):
        #the surface to draw on
        self.surface = pygame.display.get_surface()
        #our game's root
        self.root = root
        
        #this drawables image
        self.image=loaders.load_image_alpha(imgloc)
                
        #position and rotation of this drawable
        self.xpos=0
        self.ypos=0
        self.rotation=0
        
        #scale of this drawable
        self.scale=1
    #####################   END __init__   ####################
   
    ###########################################################
    ## UIDrawable::draw
    ##
    ## 
    ## Desc: draws this uidrawable to the screen
    ###########################################################
    def draw(self):
        #prepare our image using the scale
        self.orig = pygame.transform.smoothscale(self.image,(self.image.get_width()*self.scale,self.image.get_height()*self.scale))
        
        #rotate the image if needed
        if self.rotation != 0:
            self.orig = pygame.transform.rotate(self.orig,self.rotation)
        
        #prepare to draw in the correct spot, taking rotation into account
        self.drawx = self.xpos + self.root.prefs["resolution"][0]/2.0 - (self.orig.get_width()/2.0)
        self.drawy = self.ypos + self.root.prefs["resolution"][1]/2.0 - (self.orig.get_height()/2.0)
        
        #draw our final image
        self.surface.blit(self.orig,(self.drawx,self.drawy))
        
        #####################     END draw     ####################
        
###############################    END UIDrawable    #################################  



######################################################################################
## UIScreen
##
## This class maintains all of the drawable data on the user interface
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class UIScreen:
    
    ###########################################################
    ## UIScreen::__init__
    ##
    ## Constructor for the UIScreen class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root):
        #the surface to draw on
        self.surface = pygame.display.get_surface()
        
        #our game's root
        self.root = root
        
        #the collection of drawables to put on the UI
        self.uiDrawables = []        
    #####################   END __init__   ####################
    
    
    
    ###########################################################
    ## UIScreen::addGameDrawable
    ##
    ## 
    ## Desc: appends a UIDrawable to UIScreens collection
    ###########################################################
    def addUIDrawable(self,uiDrawable):
        self.uiDrawables.append(uiDrawable)
        
    ################   END addGameDrawable   ##################
    
    
    ###########################################################
    ## UIScreen::removeGameDrawable
    ##
    ## 
    ## Desc: removes a UIDrawable from UIScreens collection
    ###########################################################
    def removeUIDrawable(self,uiDrawable):
        self.uiDrawables.remove(uiDrawable)    
        
    ###############   END removeGameDrawable   ################
    
    ###########################################################
    ## UIScreen::renderScreen
    ##
    ## 
    ## Desc: draws all of the uidrawables in our collection
    ###########################################################
    def renderScreen(self):
  
        #draw all GameDrawables we have
        for d in self.uiDrawables:
            d.draw() 
              
    #####################   END __init__   ####################
    
    
######################################################################################
## Minimap
##
## This class represents a lightweight radar for players to see each other relatively
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class Minimap(UIDrawable):
    
    
    ###########################################################
    ## Minimap::__init__
    ##
    ## Constructor for the Minimap class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root,camera,mappables):
        #the surface to draw on
        self.surface = pygame.display.get_surface()
        #our games root
        self.root = root
        
        #we want to draw around our camera's position (so we can see it :p)
        self.camera = camera
        
        #all of the mappable players to keep track of on our radar
        self.mappables = mappables
        
        #if our radar is being displayed or not
        self.visible= False
    #####################   END __init__   ####################
    
    ###########################################################
    ## Minimap::update
    ##
    ## 
    ## Desc: updates the info needed by the minimap
    ###########################################################
    def update(self,camera,mappables,visible):
        #update the camera so we display our radar in the right spot
        self.camera = camera
        
        #update the player's position to maintain accuracy
        self.mappables = mappables
        
        #if visible was passed in as true, then toggle our visibility
        if visible==True:
            self.visible = ~self.visible
    #####################   END __init__   ####################
    
    ###########################################################
    ## Minimap::draw
    ##
    ## 
    ## Desc: draws the minimap onto the screen
    ###########################################################
    def draw(self):
        if self.visible:
            
            ## draw a reference to the origin on our radar ##
            
            #what direction is the origin in
            angle = math.pi+math.atan2(self.camera.ypos, self.camera.xpos)
            
            #using the angle specify the point to draw a line pointing to the origin            
            x1 = math.cos(angle)*150+self.root.prefs["resolution"][0]/2;
            y1 = math.sin(angle)*150+self.root.prefs["resolution"][1]/2;
            x2 = math.cos(angle)*175+self.root.prefs["resolution"][0]/2;
            y2 = math.sin(angle)*175+self.root.prefs["resolution"][1]/2;
            
            #draw the blue origin line
            pygame.draw.line(self.surface,pygame.Color(0,0,250),(x1,y1),(x2,y2))
            
            #draw all players in our collection
            for d in self.mappables:
                #what direction is the player in
                angle = math.pi+math.atan2(self.camera.ypos-d.ypos, self.camera.xpos-d.xpos)
                
                #make the line fade the farther away they are
                distance = math.sqrt((self.camera.ypos-d.ypos)*(self.camera.ypos-d.ypos)+ (self.camera.xpos-d.xpos)*(self.camera.xpos-d.xpos))
                color =  (1/(distance/5000+1))*255
                
                #using the angle specify the point to draw a line pointing to the player
                x1 = math.cos(angle)*150+self.root.prefs["resolution"][0]/2;
                y1 = math.sin(angle)*150+self.root.prefs["resolution"][1]/2;
                x2 = math.cos(angle)*160+self.root.prefs["resolution"][0]/2;
                y2 = math.sin(angle)*160+self.root.prefs["resolution"][1]/2;
                
                #the the line to this player in our collection
                pygame.draw.line(self.surface,pygame.Color(int(color),int(color),int(color)),(x1,y1),(x2,y2))
                
    #####################     END draw     ####################
        
###############################   End Minimap   ###########################################        
        
        
      
######################################################################################
## Mainmap
##
## This class represents a more detailed map of players/objects
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class Mainmap(UIDrawable):
    
    
    ###########################################################
    ## Mainmap::__init__
    ##
    ## Constructor for the Mainmap class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root,camera,mappables):
        # the surface to draw on
        self.surface = pygame.display.get_surface()
        # our game's root
        self.root = root
        
        #the camera for the game
        self.camera = camera
        
        #the players to put on the screen
        self.mappables = mappables
        
        #if our map is currently visible
        self.visible = False
        
    #####################   END __init__   ####################
    
    ###########################################################
    ## Mainmap::update
    ##
    ## 
    ## Desc: updates the info needed by the main map
    ###########################################################
    def update(self,camera,mappables,visible):
        #update our camera so we know where to draw
        self.camera = camera
        
        #update the players positions so we can see them on the map accurately
        self.mappables = mappables
        
        #if visible was passed in as true, then toggle our visibility
        if visible==True:
            self.visible = ~self.visible
    #####################   END __init__   ####################
    
    ###########################################################
    ## Mainmap::draw
    ##
    ## 
    ## Desc: draws the mainmap onto the screen
    ###########################################################
    def draw(self):
        if self.visible:
            
            #get a font to use to show players names above their marker
            self.font = self.root.font18
            
            ## draw each player onto the screen in a maplike fashion wioth their name above their marker ##
            for d in self.mappables:
                #draw everything in the equivalent of 100 normal sized screens away from the origin
                x1 = (d.xpos)/100+self.root.prefs["resolution"][0]/2;
                y1 = (d.ypos)/100+self.root.prefs["resolution"][1]/2;          
    
                #draw the marker for the point
                pygame.draw.line(self.surface,pygame.Color(255,0,0),(x1,y1),(x1,y1-8))
                pygame.draw.line(self.surface,pygame.Color(255,0,0),(x1-4,y1-4),(x1+4,y1-4))
                
                #draw their name above the marker
                string = str(d.username)
                self.surface.blit(self.font.render(string,True,pygame.color.Color(255,255,255)),(x1,y1))
           
            #draw our player in the equivalent of 100 normal sized screens away from the origin    
            x1 = (self.camera.xpos)/100+self.root.prefs["resolution"][0]/2
            y1 = (self.camera.ypos)/100+self.root.prefs["resolution"][1]/2
             
             
            #draw the marker for the point we're at
            pygame.draw.line(self.surface,pygame.Color(0,0,255),(x1,y1),(x1,y1-10))
            pygame.draw.line(self.surface,pygame.Color(0,0,255),(x1-5,y1-5),(x1+5,y1-5))
            pygame.draw.line(self.surface,pygame.Color(0,0,255),(x1-5,y1-10),(x1+5,y1))
            pygame.draw.line(self.surface,pygame.Color(0,0,255),(x1+5,y1-10),(x1-5,y1))
            
            
            #draw our name above the marker  
            string = str(self.root.pane.player.username)
            self.surface.blit(self.font.render(string,True,pygame.color.Color(255,255,255)),(x1,y1))
    
###############################   End Mainmap   ###########################################  


######################################################################################
## DisplayNames
##
## This class represents a UI to display names above players heads
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class DisplayNames(UIDrawable):
    
    
    ###########################################################
    ## DisplayNames::__init__
    ##
    ## Constructor for the DisplayNames class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root,camera,mappables):
        # the surface to draw on
        self.surface = pygame.display.get_surface()
        # our game's root
        self.root = root
        
        # the camera to draw things in the right spot
        self.camera = camera
        
        # other players to draw names above
        self.mappables = mappables
        
        #if we are currently displayed or not
        self.visible = False
    #####################   END __init__   ####################
    
    ###########################################################
    ## DisplayNames::update
    ##
    ## 
    ## Desc: updates the info needed to display player names
    ###########################################################
    def update(self,camera,mappables,visible):
        #update our camera so we draw in the right spot
        self.camera = camera
        
        #update the other players posisiton so we can put their name in the right spot
        self.mappables = mappables
        
        # if visible passed in as true, toggle our visibility
        if visible==True:
            self.visible = ~self.visible
    #####################   END update   ######################
    
    ###########################################################
    ## DisplayNames::draw
    ##
    ## 
    ## Desc: draws the names onto the screen
    ###########################################################
    def draw(self):
        if self.visible:
            #set the font to write in
            self.font = self.root.font18
            
            ## draw a name above every player ##
            for d in self.mappables:
                x1 = ((d.xpos - self.camera.xpos)*self.camera.zoom + self.root.prefs["resolution"][0]/2.0) - (d.orig.get_width()/2.0)+20
                y1 = ((d.ypos - self.camera.ypos)*self.camera.zoom + self.root.prefs["resolution"][1]/2.0) - (d.orig.get_height()/2.0) - 70
        
                #we want to draw their username above thier ship
                string = str(d.username)
                self.surface.blit(self.font.render(string,True,pygame.color.Color(255,255,255)),(x1,y1))
            
            ## draw a name above ourself ##
            x1 = (self.root.prefs["resolution"][0]/2.0) - (self.root.pane.player.orig.get_width()/2.0)+20
            y1 = (self.root.prefs["resolution"][1]/2.0) - (self.root.pane.player.orig.get_height()/2.0) - 70
    
            string = str(self.root.pane.player.username)
            self.surface.blit(self.font.render(string,True,pygame.color.Color(255,255,255)),(x1,y1))
            
    #####################    END draw    ######################
        
###############################   End DisplayNames   ###########################################    
        
        
######################################################################################
## DisplayChat
##
## This class represents a UI to display game chat
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class DisplayChat(UIDrawable):
    
    
    ###########################################################
    ## DisplayChats::__init__
    ##
    ## Constructor for the DisplayChat class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root):
        # the surface to draw on
        self.surface = pygame.display.get_surface()
        # our game's root
        self.root = root
        
        #the current message being inputed (if any)
        self.currentMessage=""
        
        # a collection of messages other players have made
        self.strChatMessages = []
        # a collection of the time each message was made (to fade them away of their lifetime)
        self.timeChatMessages = []
        
        #the number of messages
        self.numMessages = 0
        
        #if the current message is being displayed or not
        self.visible = True
    #####################   END __init__   ####################
    
    ###########################################################
    ## DisplayChat::update
    ##
    ## 
    ## Desc: updates the info needed to display player messages
    ###########################################################
    def update(self,strChatMessages):
        # given a collection of messages, add them to our lists
        for str in strChatMessages:
            self.strChatMessages.append(str)
            self.timeChatMessages.append(time.time())
            self.numMessages+=1
        
    #####################    END update    ####################
    
    
    ###########################################################
    ## DisplayChat::handleControls
    ##
    ## 
    ## Desc: takes user control to type messages
    ###########################################################
    def handleControls(self):
        #handle all basic characters
        for ch in range(1,255):
            if(self.root.monitor.getjustpressed(chr(ch))):
                if((self.root.monitor.getstate("RIGHT SHIFT"))or(self.root.monitor.getstate("LEFT SHIFT"))):
                   self.currentMessage+=(chr(ch))
                else:
                    self.currentMessage+=str.lower(chr(ch))
                
        #make sure they can spacebar between words
        if(self.root.monitor.getjustpressed("SPACE")):
                self.currentMessage+=" "
           
        #if theyve screwed up they can delete the last character they typed     
        if(self.root.monitor.getjustpressed("BACKSPACE")):
                self.currentMessage=self.currentMessage[0:len(self.currentMessage)-1]
                
       
    #####################    END handleControls    ####################
    
    
    ###########################################################
    ## DisplayChat::draw
    ##
    ## 
    ## Desc: draws the names onto the screen
    ###########################################################
    def draw(self):
        # set the font to use
        self.font = self.root.font18
        i=0
        
        #old messages should be removed
        remove = []
        
        ## display all of the recent chat messages ##
        for str in self.strChatMessages:
            #we position the current message
            x1 = 5
            y1 = 5+18*i
            
            #this adds a fade to older messages
            fade = int(255*(1-(time.time()-self.timeChatMessages[i])/10))
            
            #if no longer visible, just remove from the screen
            if fade <=0:
                remove.append(str)
            else:
                self.surface.blit(self.font.render(str,True,pygame.color.Color(fade,fade,fade)),(x1,y1))
            
            i+=1
            
        ## for every message that needs to be removed, remove them ##   
        for r in remove:
            indx = self.strChatMessages.index(r)
            self.strChatMessages.remove(self.strChatMessages[indx])
            self.timeChatMessages.remove(self.timeChatMessages[indx])
            
        #display the current message being typed (if any)
        if self.visible:
            self.surface.blit(self.font.render("Say: "+self.currentMessage,True,pygame.color.Color(255,255,255)),(5,self.root.prefs["resolution"][1]-150))
    
        ########################    END draw    #######################   
                
###############################   End Chat   ###########################################      

    
######################################################################################
## UIMeters
##
## This class represents a UI to display the health and shields of our ship
##
## @author(s): Alex Foran, Corey Grosz
## @documentation: Shadow
######################################################################################            
class UIMeters (UIDrawable):
    ###########################################################
    ## UIMeters::__init__
    ##
    ## Constructor for the UIMeters class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root):
        # our game's root
        self.root=root
        
        #scale the ui to the screens resolution
        self.ratiox = 1200.0/self.root.prefs["resolution"][0]
        self.ratioy = 900.0/self.root.prefs["resolution"][1]
        
        #scaling/display variables
        self.FIx = 0
        self.FIy = 0
        self.HPx = 80/self.ratiox
        self.HPy = 0
        self.SHx = 80/self.ratiox
        self.SHy = 60/self.ratioy
        
        #set the image for the frame around the hp/health
        self.frameimage=loaders.load_image_alpha(gameroot.respath+"ui/meterframes.png")
        self.frameimage=pygame.transform.smoothscale(self.frameimage,(int(self.frameimage.get_width()/self.ratiox),int(self.frameimage.get_height()/self.ratioy)))
        
        #set the image for the frame around the hp bar
        self.hpbar=loaders.load_image_alpha(gameroot.respath+"ui/healthmeter.png")
        self.hpbar=pygame.transform.smoothscale(self.hpbar,(int(self.hpbar.get_width()/self.ratiox),int(self.hpbar.get_height()/self.ratioy)))
        
        #set the image for the frame around the shields bar
        self.shbar=loaders.load_image_alpha(gameroot.respath+"ui/shieldmeter.png")
        self.shbar=pygame.transform.smoothscale(self.shbar,(int(self.shbar.get_width()/self.ratiox),int(self.shbar.get_height()/self.ratioy)))
    ###################    END __init__    ####################
    
    ###########################################################
    ## UIMeters::draw
    ##
    ## 
    ## Desc: Draws the health and shield of the player to the screen
    ###########################################################
    def draw(self):
        #get our main surface
        surface=pygame.display.get_surface()
        
        #set our main positions
        fx=0
        fy=self.root.prefs["resolution"][1]-self.frameimage.get_height()
        
        #get our hp/shield ratios to display 
        healthratio=self.root.pane.player.health/100.0
        shieldratio=self.root.pane.player.shield/100.0
        
        #draw the frame,hp, and shield bars
        surface.blit(self.hpbar,(fx+self.HPx,fy + self.HPy),pygame.Rect(0,0,(20+int(380*healthratio))/self.ratiox,60/self.ratioy))
        surface.blit(self.shbar,(fx+self.SHx,fy+self.SHy),pygame.Rect(0,0,(20+int(380*shieldratio))/self.ratiox,60/self.ratioy))
        surface.blit(self.frameimage,(self.FIx+fx,self.FIy+fy))
    #####################    END draw    ######################
        
        
        
######################################################################################
## DisplayHealth
##
## This class represents a UI to display health and shield above players heads
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class DisplayHealth(UIDrawable):
    
    
    ###########################################################
    ## DisplayHealth::__init__
    ##
    ## Constructor for the DisplayHealth class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root,camera,mappables):
        # the surface to draw on
        self.surface = pygame.display.get_surface()
        # our game's root
        self.root = root
        # the game's camera
        self.camera = camera
        # the players positions
        self.mappables = mappables
        #if this ui element is being shown or not
        self.visible = False
        
        #the image for the hp bar
        self.hpbar=loaders.load_image_alpha(gameroot.respath+"ui/healthmeter.png")
        self.hpbar=pygame.transform.smoothscale(self.hpbar,(int(self.hpbar.get_width()/10),int(self.hpbar.get_height()/10)))
        
        #the image for the shield bar
        self.shbar=loaders.load_image_alpha(gameroot.respath+"ui/shieldmeter.png")
        self.shbar=pygame.transform.smoothscale(self.shbar,(int(self.shbar.get_width()/10),int(self.shbar.get_height()/10)))
    
    #####################   END __init__   ####################
    
    ###########################################################
    ## DisplayHealth::update
    ##
    ## 
    ## Desc: updates the info needed to display player names
    ###########################################################
    def update(self,camera,mappables,visible):
        #update our cameras position
        self.camera = camera
        #update the players positions
        self.mappables = mappables
        
        #if visible is passed in as true, toggle our visibility
        if visible==True:
            self.visible = ~self.visible
    #####################   END __init__   ####################
    
    ###########################################################
    ## DisplayHealth::draw
    ##
    ## 
    ## Desc: draws the health and shields over players' ship
    ###########################################################
    def draw(self):
        if self.visible:
            ## display other player's ships hp/shield bar ##
            for d in self.mappables:
                #get their percent damage on both health and shields
                healthratio=d.health/100.0
                shieldratio=d.shield/100.0
        
                #set the position of the bars
                x1 = ((d.xpos - self.camera.xpos)*self.camera.zoom + self.root.prefs["resolution"][0]/2.0) - (d.orig.get_width()/2.0)+20
                y1 = ((d.ypos - self.camera.ypos)*self.camera.zoom + self.root.prefs["resolution"][1]/2.0) - (d.orig.get_height()/2.0) - 50
        
                #draw the bars
                self.surface.blit(self.hpbar,(x1,y1),pygame.Rect(0,0,(int(38*healthratio)),6))
                self.surface.blit(self.shbar,(x1,y1+5),pygame.Rect(0,0,(int(38*shieldratio)),6))
                
            #get our percent damage on both health and shields
            healthratio=self.root.pane.player.health/100.0
            shieldratio=self.root.pane.player.shield/100.0
 
            #set the position of the bars
            x1 = (self.root.prefs["resolution"][0]/2.0) - (self.root.pane.player.orig.get_width()/2.0)+20
            y1 = (self.root.prefs["resolution"][1]/2.0) - (self.root.pane.player.orig.get_height()/2.0) - 50
    
            #draw the bars
            self.surface.blit(self.hpbar,(x1,y1),pygame.Rect(0,0,(int(38*healthratio)),6))
            self.surface.blit(self.shbar,(x1,y1+5),pygame.Rect(0,0,(int(38*shieldratio)),6))
        
###############################   End Minimap   ###########################################
        

######################################################################################
## DisplayLeaderboard
##
## This class represents a UI to display the games leaderboard
##
## @author(s): Shadow
## @documentation: Shadow
######################################################################################
class DisplayLeaderboard(UIDrawable):
    
    
    ###########################################################
    ## DisplayLeaderboard::__init__
    ##
    ## Constructor for the DisplayLeaderboard class
    ## Desc: Initializes all of the variables
    ###########################################################
    def __init__(self,root):
        # surface to draw on
        self.surface = pygame.display.get_surface()
        # our game's root
        self.root = root
        
        #the messages to put on the board
        self.strBoardMessages = []
        
        #the number of messages we have
        self.numMessages = 0
        
        #if we are visible or not
        self.visible = False
    #####################   END __init__   ####################
    
    ###########################################################
    ## DisplayLeaderboard::update
    ##
    ## 
    ## Desc: updates the info needed to display the leaderboard
    ###########################################################
    def update(self,strBoardMessages,visible):
        if len(strBoardMessages)!=0:
        
            #reset our messages
            self.numMessages = 0
            self.strBoardMessages = []
            self.strBoardMessages.append("Player            Kills            Deaths")
            
            #append the messages from the leaderboard
            for st in strBoardMessages:
                self.strBoardMessages.append(str(st['player']) + "            " + str(st['kills']) + "            " + str(st['deaths']))
                self.numMessages+=1
        
        #if visible was passed in as true, toggle our visibility
        if visible==True:
            self.visible = ~self.visible
        
    #####################    END update    ####################
    
    ###########################################################
    ## DisplayLeaderboard::draw
    ##
    ## 
    ## Desc: draws the leaderboard onto the screen
    ###########################################################
    def draw(self):
        if self.visible:
            #set our font
            self.font = self.root.font24
            i=0
            ## display the leaderboard ##
            for str in self.strBoardMessages:
                x1 = self.root.prefs["resolution"][0]/4.0
                y1 = 50+24*i
                i+=1
                
          
                self.surface.blit(self.font.render(str,True,pygame.color.Color(255,255,255)),(x1,y1))
                
    ######################    END draw    #####################
            
                
###############################   End Chat   ###########################################